【巡检问题分析与最佳实践】RDS PostgreSQL 慢SQL问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 慢SQL会消耗较多的数据库资源,很容易造成数据库的负载增加,慢SQL的问题直接影响着数据库的性能,排查慢SQL问题,首先要找到RDS PG中的慢SQL,在学习EXPLAIN的语法,学会阅读执行计划,并对SQL进行优化。

往期分享

RDS MySQL 小版本升级最佳实践

RDS MySQL 实例空间问题

RDS MySQL 内存使用问题

RDS MySQL 活跃线程数高问题

RDS MySQL 慢SQL问题

RDS MySQL 实例IO高问题

概述

慢SQL会消耗较多的数据库资源,很容易造成数据库的负载增加,慢SQL的问题直接影响着数据库的性能,排查慢SQL问题,首先要找到RDS PG中的慢SQL,在学习EXPLAIN的语法,学会阅读执行计划,并对SQL进行优化。

找到慢SQL

控制慢日志时长的参数为log_min_duration_statement,单位为ms,默认值为超过1秒的SQL会收集在慢SQL日志中进行展示。

1.png


慢SQL日志查看

2.png


通过开启auto_explain.log_analyze、auto_explain.log_buffers、auto_explain.log_min_duration,可以在慢日志中打印慢SQL的执行计划。

3.png

EXPLAIN语法


EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement
这里 option可以是:
    ANALYZE [ boolean ]
    VERBOSE [ boolean ]
    COSTS [ boolean ]
    BUFFERS [ boolean ]
    TIMING [ boolean ]
    SUMMARY [ boolean ]
    FORMAT { TEXT | XML | JSON | YAML }


这个命令显示PostgreSQL计划器为提供的语句所生成的执行计划。该执行计划会显示将怎样扫描语句中引用的表 — 普通的顺序扫描、索引扫描等等 — 以及在引用多个表时使用何种连接算法来把来自每个输入表的行连接在一起。


postgres=# explain select * from test;
                     QUERY PLAN
----------------------------------------------------
 Seq Scan on test  (cost=0.00..1.01 rows=1 width=4)
(1 row)


显示中最重要的部分是估计出的语句执行代价,它是计划器对于该语句要运行多久的猜测(以任意的代价单位度量,但是习惯上表示取磁盘页面的次数)。事实上会显示两个数字:在第一行能被返回前的启动代价,以及返回所有行的总代价。对于大部分查询来说总代价是最重要的,但是在一些情景中(如EXISTS中的子查询),计划器将选择更小的启动代价来代替最小的总代价(因为执行器将在得到一行后停止)。此外,如果你用一个LIMIT子句限制返回行的数量,计划器会在终端代价之间做出适当的插值来估计到底哪个计划是真正代价最低的。


EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous; 
QUERY PLAN 
------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1) 
  Sort Key: t1.fivethous 
  Sort Method: quicksort Memory: 77kB 
  -> Hash Join (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1) 
       Hash Cond: (t2.unique2 = t1.unique2) 
       -> Seq Scan on tenk2 t2 (cost=0.00..445.00 rows=10000 width=244) (actual time=0.007..2.583 rows=10000 loops=1) 
       -> Hash (cost=229.20..229.20 rows=101 width=244) (actual time=0.659..0.659 rows=100 loops=1) 
            Buckets: 1024 Batches: 1 Memory Usage: 28kB 
            -> Bitmap Heap Scan on tenk1 t1 (cost=5.07..229.20 rows=101 width=244) (actual time=0.080..0.526 rows=100 loops=1) 
                 Recheck Cond: (unique1 < 100) 
                 -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) (actual time=0.049..0.049 rows=100 loops=1) 
                      Index Cond: (unique1 < 100) 
Planning time: 0.194 ms 
Execution time: 8.008 ms


  • analyze:执行命令并且显示实际的运行时间和其他统计信息。参数默认值为FALSE
  • verbose:显示关于计划的额外信息。特别是:计划树中每个结点的输出列列表、模式限定的表和函数名、总是把表达式中的变量标上它们的范围表别名,以及总是打印统计信息被显示的每个触发器的名称。这个参数默认被设置为FALSE。
  • costs:包括每一个计划结点的估计启动和总代价,以及估计的行数和每行的宽度。这个参数默认被设置为TRUE。
  • buffers:包括缓冲区使用的信息。特别是:共享块命中、读取、标记为脏和写入的次数、本地块命中、读取、标记为脏和写入的次数、以及临时块读取和写入的次数。一次命中表示避免了一次读取,因为需要的块已经在缓存中找到了。共享块包含着来自于常规表和索引的数据,本地块包含着来自于临时表和索引的数据,而临时块包含着在排序、哈希、物化计划结点和类似情况中使用的短期工作数据。脏块的数量表示被这个查询改变的之前未被修改块的数量,而写入块的数量表示这个后台在查询处理期间从缓存中替换出去的脏块的数量。为一个较高层结点显示的块数包括它的所有子结点所用到的块数。在文本格式中,只会打印非零值。只有当ANALYZE也被启用时,这个参数才能使用。它的默认被设置为FALSE。
  • timing:在输出中包括实际启动时间以及在每个结点中花掉的时间。反复读取系统时钟的负荷在某些系统上会显著地拖慢查询,因此在只需要实际的行计数而不是实际时间时,把这个参数设置为FALSE可能会有用。即便用这个选项关闭结点层的计时,整个语句的运行时间也总是会被度量。只有当ANALYZE也被启用时,这个参数才能使用。它的默认被设置为TRUE。
  • summary:在查询计划之后包含摘要信息(例如,总计的时间信息)。当使用ANALYZE 时默认包含摘要信息,但默认情况下不包含摘要信息,但可以使用此选项启用摘要信息。 使用EXPLAIN EXECUTE中的计划时间包括从缓存中获取计划所需的时间 以及重新计划所需的时间(如有必要)。
  • format:指定输出格式,可以是 TEXT、XML、JSON 或者 YAML。非文本输出包含和文本输出格式相同的信息,但是更容易被程序解析。这个参数默认被设置为TEXT。

如何阅读执行计划

从上面的例子来看,EXPLAIN 命令的输出可以看做是一个树形结构,我们称之为查询计划树,树的每个节点包括对应的节点类型,作用对象以及其他属性例如cost、rows、width 等。如果只显示节点类型,上面的例子可以简化为如下结构:


Sort 
└── Hash Join 
          ├── Seq Scan
          └── Hash
                     └── Bitmap Heap Scan
                                └── Bitmap Index Scan


阅读执行计划是查询计划从下往上阅读,每个节点执行返回的结果会传递给父节点,比如这个例子,首先是进行Bitma Index Scan,然后将结果传递给Bitmap Heap Scan,然后传递给Hash节点。Seq Scan是与Hash平级的节点,Hash和Seq Scan两个节点将结果传递给Hash Join。Hash Join将结果传递给Sort。

在EXPLAIN 命令的输出结果中可能包含多种类型的执行节点,可以大体分为几大类:

  1. 控制节点(Control Node)
  2. 扫描节点(ScanNode)
  3. 物化节点(Materialization Node)
  4. 连接节点(Join Node)


下面详细介绍一下扫描节点,扫描节点狭义的讲指的是扫描表中的数据,实际也包含扫描函数结果集、子查询结果等。目前支持的扫描节点如下:

  • Seq Scan,顺序扫描
  • Index Scan,基于索引扫描,但不只是返回索引列的值
  • IndexOnly Scan,基于索引扫描,并且只返回索引列的值,简称为覆盖索引
  • BitmapIndex Scan,利用Bitmap 结构扫描
  • BitmapHeap Scan,把BitmapIndex Scan 返回的Bitmap 结构转换为元组结构
  • Tid Scan,用于扫描一个元组TID 数组
  • Subquery Scan,扫描一个子查询
  • Function Scan,处理含有函数的扫描
  • TableFunc Scan,处理tablefunc 相关的扫描
  • Values Scan,用于扫描Values 链表的扫描
  • Cte Scan,用于扫描WITH 字句的结果集
  • NamedTuplestore Scan,用于某些命名的结果集的扫描
  • WorkTable Scan,用于扫描Recursive Union 的中间数据
  • Foreign Scan,用于外键扫描
  • Custom Scan,用于用户自定义的扫描


下面重点介绍常用的几个:Seq Scan、Index Scan、IndexOnly Scan、BitmapIndex Scan、BitmapHeap Scan

Seq Scan 是全表顺序扫描,一般查询没有索引的表需要全表顺序扫描,例如下面的EXPLAIN 输出:

postgres=> explain(ANALYZE,VERBOSE,BUFFERS) select * from class where st_no=2; 
QUERY PLAN 
--------------------------------------------------------------------------------------------------
Seq Scan on public.class (cost=0.00..26.00 rows=1 width=35) (actual time=0.136..0.141 rows=1 loops=1) 
    Output: st_no, name 
    Filter: (class.st_no = 2) 
    Rows Removed by Filter: 1199 
    Buffers: shared hit=11 
Planning time: 0.066 ms 
Execution time: 0.160 ms


  • Seq Scan on public.class 表明了这个节点的类型和作用对象,即在class 表上进行了全表扫描。
  • (cost=0.00..26.00 rows=1 width=35) 表明了这个节点的代价估计,0.00是节点启动成本,26.00为该节点的代价,rows是该节点输出行的估计值,width是该节点输出行的平均宽度。
  • (actual time=0.136..0.141 rows=1 loops=1) 表明了这个节点的真实执行信息,0.136表示该节点启动时间单位是ms。0.141表示该节点耗时单位是ms。rows代表实际输出行。loops代表节点循环次数。
  • Output: st_no, name 表明了SQL 的输出结果集的各个列,当EXPLAIN 命令中的选项VERBOSE 为on时才会显示。
  • Filter: (class.st_no = 2) 表明了Seq Scan 节点之上的Filter 操作,即全表扫描时对每行记录进行过滤操作,过滤条件为class.st_no = 2
  • Rows Removed by Filter: 1199 表明了过滤操作过滤了多少行记录,属于Seq Scan 节点的VERBOSE 信息,只有EXPLAIN 命令中的VERBOSE 选项为on 时才会显示
  • Buffers: shared hit=11 表明了从共享缓存中命中了11 个BLOCK,属于Seq Scan 节点的BUFFERS 信息,只有EXPLAIN 命令中的BUFFERS 选项为on 时才会显示
  • Planning time: 0.066 ms 表明了生成查询计划的时间
  • Execution time: 0.160 ms 表明了实际的SQL 执行时间,其中不包括查询计划的生成时间


Index Scan 是索引扫描,主要用来在WHERE 条件中存在索引列时的扫描,如上面Seq Scan 中的查询如果在st_no 上创建索引,则EXPLAIN 输出如下:


QUERY PLAN 
--------------------------------------------------------------------------------------------------
Index Scan using no_index on public.class (cost=0.28..8.29 rows=1 width=35) (actual time=0.022..0.023 rows=1 loops=1) 
     Output: st_no, name 
     Index Cond: (class.st_no = 2) 
     Buffers: shared hit=3 
Planning time: 0.119 ms 
Execution time: 0.060 ms 
(6 rows)


Index Scan using no_index on public.class 表明是使用的public.class 表的no_index 索引对表进行索引扫描的

Index Cond: (class.st_no = 2) 表明索引扫描的条件是class.st_no = 2

可以看出,使用了索引之后,对相同表的相同条件的扫描速度变快了。这是因为从全表扫描变为索引扫描,通过Buffers: shared hit=3 可以看出,需要扫描的BLOCK(或者说元组)少了,所以需要的代价也就小了,速度也就快了。


IndexOnly Scan 是覆盖索引扫描,所需的返回结果能被所扫描的索引全部覆盖,如上面Index Scan中的SQL 把“select * ” 修改为“select st_no” ,其EXPLAIN 结果输出如下:


postgres=> explain(ANALYZE,VERBOSE,BUFFERS) select st_no from class where st_no=2; 
QUERY PLAN 
--------------------------------------------------------------------------------------------------
Index Only Scan using no_index on public.class (cost=0.28..4.29 rows=1 width=4) (actual time=0.015..0.016 rows=1 loops=1) 
    Output: st_no Index Cond: (class.st_no = 2) 
    Heap Fetches: 0 
    Buffers: shared hit=3 
Planning time: 0.058 ms 
Execution time: 0.036 ms 
(7 rows)


Index Only Scan using no_index on public.class 表明使用public.class 表的no_index 索引对表进行覆盖索引扫描。Heap Fetches 表明需要扫描数据块的个数。虽然Index Only Scan 可以从索引直接输出结果。但是因为PostgreSQL MVCC 机制的实现,需要对扫描的元组进行可见性判断,即检查visibility MAP 文件。当新建表之后,如果没有进行过vacuum和autovacuum操作,这时还没有VM文件,而索引并没有保存记录的版本信息,索引Index Only Scan 还是需要扫描数据块(Heap Fetches 代表需要扫描的数据块个数)来获取版本信息,这个时候可能会比Index Scan 慢。


Bitmap Index Scan 与Index Scan 很相似,都是基于索引的扫描,但BitmapIndex Scan 节点每次执行返回的是一个位图而不是一个元组,位图中每位代表了一个扫描到的数据块。而BitmapHeap Scan一般会作为BitmapIndex Scan 的父节点,将BitmapIndex Scan 返回的位图转换为对应的元组。这样做最大的好处就是把Index Scan 的随机读转换成了按照数据块的物理顺序读取,在数据量比较大的时候,这会大大提升扫描的性能。EXPLAIN结果输出如下:


postgres=> explain(ANALYZE,VERBOSE,BUFFERS) select * from class where st_no=2; 
QUERY PLAN 
--------------------------------------------------------------------------------------------------
Bitmap Heap Scan on public.class (cost=4.29..8.30 rows=1 width=35) (actual time=0.025..0.025 rows=1 loops=1) Output: st_no, name 
Recheck Cond: (class.st_no = 2) 
Heap Blocks: exact=1 
Buffers: shared hit=3 
-> Bitmap Index Scan on no_index (cost=0.00..4.29 rows=1 width=0) (actual time=0.019..0.019 rows=1 loops=1) Index Cond: (class.st_no = 2) 
Buffers: shared hit=2 
Planning time: 0.088 ms 
Execution time: 0.063 ms 
(10 rows)


  • Bitmap Index Scan on no_index 表明使用no_index 索引进行位图索引扫描
  • Index Cond: (class.st_no = 2) 表明位图索引的条件为class.st_no = 2
  • Bitmap Heap Scan on public.class 表明对public.class 表进行Bitmap Heap 扫描
  • Recheck Cond: (class.st_no = 2) 表明Bitmap Heap Scan 的Recheck操作 的条件是class.st_no = 2,这是因为Bitmap Index Scan 节点返回的是位图,位图中每位代表了一个扫描到的数据块,通过位图可以定位到一些符合条件的数据块(这里是3,Buffers: shared hit=3),而Bitmap Heap Scan 则需要对每个数据块的元组进行Recheck
  • Heap Blocks: exact=1 表明准确扫描到数据块的个数是1
  • 一般来说:
  • 大多数情况下,Index Scan 要比 Seq Scan 快。但是如果获取的结果集占所有数据的比重很大时,这时Index Scan 因为要先扫描索引再读表数据反而不如直接全表扫描来的快。
  • 如果获取的结果集的占比比较小,但是元组数很多时,可能Bitmap Index Scan 的性能要比Index Scan 好。
  • 如果获取的结果集能够被索引覆盖,则Index Only Scan 因为不用去读数据,只扫描索引,性能一般最好。但是如果VM 文件未生成,可能性能就会比Index Scan 要差。


SQL优化示例


无索引


canno=> explain analyze select * from t1 where id =1;
                                                    QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1000.00..97331.31 rows=1 width=4) (actual time=0.217..302.316 rows=1 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on t1  (cost=0.00..96331.21 rows=1 width=4) (actual time=191.208..289.240 rows=0 loops=3)
         Filter: (id = 1)
         Rows Removed by Filter: 3333333
 Planning Time: 0.030 ms
 Execution Time: 302.381 ms
(8 rows)
通过计划可以看出是全表扫描的t1表,过滤条件是id=1;
针对t1(id)创建索引
create index on t1(id)
canno=> explain analyze select * from t1 where id =1;
                                                    QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
 Index Only Scan using t1_id_idx on t1  (cost=0.43..2.45 rows=1 width=4) (actual time=0.035..0.036 rows=1 loops=1)
   Index Cond: (id = 1)
   Heap Fetches: 1
 Planning Time: 0.134 ms
 Execution Time: 0.052 ms
(5 rows)
查看新的执行计划,时间缩短至0.052ms。


不是最优索引


create table t2(id int,name int);
insert into t2 select random()*(id/100),random()*(id/100) from generate_series(1,1000000) t(id);
create index on t2(id)
canno=> explain analyze select * from t2 where id=10 and name=13;
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Index Scan using t2_id_idx on t2  (cost=0.42..12.12 rows=1 width=8) (actual time=0.098..2.631 rows=88 loops=1)
   Index Cond: (id = 10)
   Filter: (name = 13)
   Rows Removed by Filter: 4461
 Planning Time: 0.081 ms
 Execution Time: 2.655 ms
(6 rows)
查看计划发现已经做了索引扫描,但是还是存在name字段的条件过滤。可以增加一个id+name的索引。
create index on t2(id,name);
canno=> explain analyze select * from t2 where id=10 and name=13;
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Index Only Scan using t2_id_name_idx on t2  (cost=0.42..15.48 rows=18 width=8) (actual time=0.028..0.134 rows=88 loops=1)
   Index Cond: ((id = 10) AND (name = 13))
   Heap Fetches: 88
 Planning Time: 0.198 ms
 Execution Time: 0.157 ms
(5 rows)
可以看出时间从2.6ms降低至0.157ms


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3天前
|
SQL 消息中间件 关系型数据库
实时计算 Flink版产品使用合集之 sql采集mysql能拿到before的数据吗
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
6天前
|
SQL 存储 关系型数据库
RDS for MySQL的SQL分类与数据类型
小明需在MySQL中管理商品信息,使用SQL完成业务操作。SQL分为DQL(查询)、DML(增删改)、DDL(定义)、DCL(权限控制)和TCL(事务)五大类。DDL用于创建、修改和删除数据库结构,DML处理数据,DCL控制权限,TCL管理事务,DQL则用于查询数据。MySQL有多种数据类型,如数值型(整数、小数)、日期型和字符串型等,选择合适的数据类型是高效开发的关键。
16 0
|
2天前
|
关系型数据库 分布式数据库 数据库
【阿里云云原生专栏】云原生时代的数据库选型:阿里云RDS与PolarDB对比分析
【5月更文挑战第24天】阿里云提供RDS和PolarDB两种数据库服务。RDS是高性能的在线关系型数据库,支持MySQL等引擎,适合中小规模需求;而PolarDB是分布式数据库,具备高扩展性和性能,适用于大规模数据和高并发场景。RDS与PolarDB在架构、性能、弹性伸缩、成本等方面存在差异,开发者应根据具体需求选择。示例代码展示了如何通过CLI创建RDS和PolarDB实例。
185 0
|
2天前
|
SQL 关系型数据库 MySQL
mysql插入500条数据sql语句
【5月更文挑战第12天】
|
2天前
|
SQL 存储 关系型数据库
MySQL进阶-增删查改(全网最详细sql教学)-3
MySQL进阶-增删查改(全网最详细sql教学)
10 0
|
2天前
|
SQL 关系型数据库 MySQL
MySQL进阶-增删查改(全网最详细sql教学)-2
MySQL进阶-增删查改(全网最详细sql教学)
9 0
|
2天前
|
SQL NoSQL 关系型数据库
MySQL进阶-增删查改(全网最详细sql教学)-1
MySQL进阶-增删查改(全网最详细sql教学)
9 0
|
3天前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用合集之sql读取mysql写入clickhouse,该如何操作
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
3天前
|
SQL 关系型数据库 MySQL
MySQL中的12个SQL编写规范
SQL良好习惯提升查询清晰度、效率和安全性,包括使用EXPLAIN分析查询计划、DELETE/UPDATE时加LIMIT限制影响范围、为表和字段添加注释、关键字大写缩进、指定INSERT字段名、先测试后执行、表含主键及时间戳字段、Update/Delete需Where条件、用InnoDB引擎、避免SELECT *,选择UTF8字符集和规范索引命名。
MySQL中的12个SQL编写规范
|
3天前
|
SQL 关系型数据库 数据库
阿里云数据库 RDS SQL Server版实战【性能优化实践、优点探析】
本文探讨了Amazon RDS SQL Server版在云数据库中的优势,包括高可用性、可扩展性、管理便捷、安全性和成本效益。通过多可用区部署和自动备份,RDS确保数据安全和持久性,并支持自动扩展以适应流量波动。可视化管理界面简化了监控和操作,而数据加密和访问控制等功能保障了安全性。此外,弹性计费模式降低了运维成本。实战应用显示,RDS SQL Server版能有效助力企业在促销高峰期稳定系统并保障数据安全。阿里云的RDS SQL Server版还提供了弹性伸缩、自动备份恢复、安全性和高可用性功能,进一步优化性能和成本控制,并与AWS生态系统无缝集成,支持多种开发语言和框架。
23 2

相关产品

  • 云数据库 RDS MySQL 版
  • 云原生数据库 PolarDB